<?php
/**
 * 	简单的模板引擎
 * 	PHP5 以上版本
 * 		循环语句
 * 			 {loop $array $val}...{/loop}
 * 			 {loop $array $key => $val}...{/loop}
 * 		if条件语句
 * 			{if $val1 ==(与PHP相同) $val2}...{/if}
 * 			{if $val1 ==(与PHP相同) $val2}...{/else}...{/if}
 * 		变量统一加上大括号
 * 			{$var1}
 * @author lanjuzi
 * @license http://www.lanjuzi.com/
 * @version 1.1
 * @copyright ©2010 lanjuzi
 */
	/*
	 * 更新时间 2010-12-15
	 * 
	 * 更新内容
	 * 1、模板路径设置成员名称有原来的$path改为 $tplPath
	 * 2、更新原有的公共属性 除了$vars 其他都改成了私有属性
	 * 3、更新原有用于设置成员值的方法统一换成了魔术方法设置，以及格式化输入的值，
	 * 		设置各种目录路径不一定要在最后加上反斜杠，程序自动判断
	 * 4、原来用file_get_contents和file_put_contents读取和写入文件的函数用新增的
	 * 		fileGetContents和filePutContents替换，提高效率
	 * 5、去掉了$getCache和$putCache方法
	 * 		用fileGetContents和filePutContentsf获取和写入
	 * 6、优化错误提示，使用showError开启错误提示
	 * 8、增加了编译后文件扩展名和模板文件扩展名设置$tplExt
	 * 		$compileExt 设置编译后文件扩展名，默认.php
	 * 		$tplExt 设置模板文件扩展名，默认为空
	 */
class eTemplate {
	private $vars = array();
	private $cacheTime = 20;
	private $tplPath = '';
	private $cachePath = '';
	private $compilePath = '';
	private $tplExt = '';
	private $cached = false;
	private $showError = false;
	private $compileExt = '.php';
	
	public function __construct($tplpath = null) {
		$this->tplPath = empty($tplpath) ? null : realpath($tplpath) . '/';
	}
	/**
	 * set($key, $val)
	 * 定义模板变量
	 * 	$key 为字符串
	 * 		与PHP变量命名规范一样
	 * 	$val 为变量的值，可以是数组 （暂不支持对象）
	 * @param $key 模板变量名称
	 * @param $val 模板变量值
	 */
	public function set($key, $val) {
		$this->vars[$key] = $val;
		return;
	}
	
	/**
	 * template($tmplname)
	 * 调用方法读取模板或者缓存内容
	 * 	根据$this->cached判断是否读取缓存
	 * @param $tmplname 模板文件名
	 * @return 模板或者缓存内容
	 */
	public function template($tmplname) {
		$tmplname = $tmplname . $this->tplExt;
		if ($this->cached !== false) {
			$content = $this->getCacheTemplate($tmplname);
		} else {
			$content = $this->getTemplate($tmplname);
		}
		return $content;
	}
	
	/**
	 * __set($key, $value)
	 * 	用来设置目录路径以及一些其他参数，同时格式化此参数
	 * @param $key 名称
	 * @param $value 值
	 * @return 无返回
	 */
	public function __set($key, $value) {
		switch ($key) {
			case 'cached' : 
				$this->$key = $value || false;
				break;
			case 'cacheTime' :
				$this->$key = intval($value);
				break;
			case 'tplExt' :
			case 'compileExt' :
				if (!empty($value)) {
					$this->$key = $value{0} === '.' ? $value : '.' . $value;
				} else {
					$this->error('扩展名不能为空');
				}
				break;
			case 'tplPath' :
				$this->$key = realpath($value) . '/';
				break;
			case 'cachePath' :
				$this->$key = realpath($value) . '/';
				break;
			case 'compilePath' :
				$this->$key = realpath($value) . '/';
				break;
			case 'showError' :
				$this->$key = $value || false;
				break;
			default :
				$this->error("“{$key}” 没有这个值");
				break;
		}
	}
	
	/**
	 * getCacheTemplate($tmplname)
	 * 判断缓存文件是否存在以及是否过期
	 * 		判断缓存是否存在，如果存在再判断缓存时间是否过期，
	 * 		过期则删除缓存再重新写入
	 * 		不存在则调用$this->getCache()读入缓存，
	 * 		然后调用$this->putCache()写入缓存
	 * @param $tmplname 模板文件名
	 * @return 缓存文件内容
	 */
	protected function getCacheTemplate($tmplname) {
		$cachename = $this->cachePath . md5($tmplname) . '.html';
		if (file_exists($cachename)) {
			$filecachetime = filemtime($cachename);
			$cachetime = $filecachetime + $this->cacheTime;
			if ($cachetime >= time()) {
				$content = $this->fileGetContents($cachename);
			} else {
				unlink($cachename);
				$content = $this->getTemplate($tmplname);
				$this->filePutContents($cachename, $content);
			}
		} else {
			$content = $this->getTemplate($tmplname);
			$this->filePutContents($cachename, $content);
		}
		return $content;
	}
	
	/**
	 * getTemplate($tmplname)
	 * 读取模板内容并编译模板
	 * @param $tmplname 模板文件名
	 * @return 模板文件内容
	 */
	private function getTemplate($tmplname) {
		$tmpl = $this->tplPath . $tmplname;
		if (file_exists($tmpl)) {
			$vars = $this->vars;
			$compilefile = $this->compilePath . md5($tmplname) . $this->compileExt;
			// 读取模板内容
			$content = $this->fileGetContents($tmpl);
			$content = $this->compile($content);
			// 写入编译后的文件
			$this->filePutContents($compilefile, $content);
			ob_start();
			include_once $compilefile;
			$content = ob_get_contents();
			ob_end_clean();
			
			return $content;
		} else {
			$this->error('模板文件不存在');
		}
	}
	
	/**
	 * compile($content)
	 * 编译内容
	 * 		传入没有编译之前的模板内容，经过编译之后返回
	 * @param $content 没编译的模板内容
	 * @return 返回编译之后的内容
	 */
	private function compile($content) {
		try {
			$loopk = '/\{loop\s+\$(\w+)\s+\$(\w+)\\s*=\>\s*\$(\w+)\s*\}\r*\n*([\s\S]*)\r*\n*(\{\/loop\})/i';
			$loop = '/\{loop\s+\$(\w+)\s+\$(\w+)\s*\}\r*\n*([\s\S]*)\r*\n*(\{\/loop\})/i';
			$pattern = array(
				$loopk,
				$loop,
				'/\{if\s+\$(\w+)\s*([\=\!]\=\=?)\s*\$(\w+)\s*\}([\s\S]*)\{\/else\}([\s\S]*)\{\/if\}/i',
				'/\{if\s+\$(\w+)\s*([\=\!]\=\=?)\s*\$(\w+)\s*\}([\s\S]*)\{\/if\}/i',
				'/\{\$(\w+)\}/i'
			);
			$replacement = array(
				"<?php foreach (\$vars['\\1'] as \$vars['\\2'] => \$vars['\\3']) {?>\\4<?php }?>",
				"<?php foreach (\$vars['\\1'] as \$vars['\\2']) {?>\\3<?php }?>",
				"<?php if(\$vars['\\1']\\2\$vars['\\3']) {?>\\4<?php }else{?>\\5<?php }?>",
				"<?php if(\$vars['\\1']\\2\$vars['\\3']) {?>\\4<?php }?>",
				"<?php echo \$vars['\\1'];?>"
			);
			do {	// 开始编译，使用循环是为了编译多重循环
				$content = preg_replace($pattern, $replacement, $content);
			} while (preg_match($loop, $content) || preg_match($loopk, $content));
			return $content;
		} catch(Exception $e) {
			$this->error($e->getMessage());
		}
	}
	/**
	 * fileGetContents($filename)
	 * 读取文件内容
	 * 		类似file_get_contents
	 * @param $filename 文件完整名称
	 * @return 成功返回读取到的文件内容，否则返回false
	 */
	private function fileGetContents($filename) {
		$handle = fopen($filename, 'r');
		if ($handle) {
			$content = fread($handle, filesize($filename));
			fclose($handle);
			return $content;
		} else {
			$this->error("“{$filename}”，文件无法打开，请检查是否有权限读取此文件");
			return false;
		}
	}
	
	/**
	 * filePutContents($filename, $data)
	 * 给文件写入内容
	 * 		类似file_put_contents
	 * @param $filename 文件完整名称
	 * @param $data 要写入的内容
	 * @return 成功返回true， 否则返回false
	 */
	private function filePutContents($filename, $data) {
		$handle = fopen($filename, 'w');
		if ($handle) {
			fwrite($handle, $data);
			fclose($handle);
			return true;
		} else {
			$this->error("“{$filename}”，文件写入错误，请检查是否有权限修改此文件");
			return false;
		}
	}
	
	/**
	 * error($errorMessage)
	 * 打印错误信息
	 * @param $errorMessage 错误提示
	 * @return 无返回值
	 */
	private function error($errorMessage) {
		if ($this->showError) {
			echo $errorMessage . '<hr />';
		}
		return;
	}
}